package util

import (
	"context"
	"os"
	"time"

	"github.com/golang/glog"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"

	"eccgateway/pkg/config"
)

var hostname string
var id string
var stopChs []chan struct{}
var fns []func(stopCh chan struct{})

const (
	leaseDuration = 15 * time.Second
	renewDeadline = 10 * time.Second
	retryPeriod   = 5 * time.Second
)

func init() {
	fns = []func(stopCh chan struct{}){}
}

func RegisterFunc(fn func(stopCh chan struct{})) {
	fns = append(fns, fn)
	stopChs = append(stopChs, make(chan struct{}))
}

func StartLeaderElection() {
	var err error
	var cfg *rest.Config

	masterURL := config.GetConfig().String(config.MasterURLKey)
	kubeconfig := config.GetConfig().String(config.KubeConfigKey)
	lockObjectNamespace := config.GetConfig().String(config.NamespaceKey)
	lockName := config.GetConfig().String(config.LeaderElectionLockNameKey)
	if masterURL == "" && kubeconfig == "" {
		cfg, err = rest.InClusterConfig()
		if err != nil {
			glog.Fatalf("Error building kubeconfig in cluster: %s", err.Error())
		}
	} else {
		cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
		if err != nil {
			glog.Fatalf("Error building kubeconfig by kube config file: %s", err.Error())
		}
	}

	leaderElectionClient, err := kubernetes.NewForConfig(rest.AddUserAgent(cfg, "leader-election"))
	if err != nil {
		glog.Fatalf("Error building leader election client: %s", err.Error())
	}

	hostname, err = os.Hostname()
	if err != nil {
		glog.Fatalf("unable to get hostname: %v", err)
	}
	// add a uniquifier so that two processes on the same host don't accidentally both become active
	id = hostname + "_" + string(uuid.NewUUID())

	rl, err := resourcelock.New(resourcelock.ConfigMapsResourceLock,
		lockObjectNamespace,
		lockName,
		leaderElectionClient.CoreV1(),
		leaderElectionClient.CoordinationV1(),
		resourcelock.ResourceLockConfig{
			Identity: id,
		})
	if err != nil {
		glog.Fatalf("couldn't create resource lock: %v", err)
	}

	run := func(ctx context.Context) {
		glog.Infof("%v successfully acquired lease", id)
		for k, fn := range fns {
			go fn(stopChs[k])
		}
	}

	glog.Infof("start leader election")
	leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
		Lock:          rl,
		LeaseDuration: leaseDuration,
		RenewDeadline: renewDeadline,
		RetryPeriod:   retryPeriod,
		Callbacks: leaderelection.LeaderCallbacks{
			OnStartedLeading: run,
			OnStoppedLeading: func() {
				glog.Infof("leaderelection lost, wait 3 seconds to start next leader election")
				for _, ch := range stopChs {
					ch <- struct{}{}
				}
				time.Sleep(time.Second * 3)
				go StartLeaderElection()
			},
		},
	})
}

func GetHostname() string {
	return hostname
}
